package com.joris.universal;

import java.util.ArrayList;
import java.util.LinkedList;

import com.rsbuddy.script.methods.Game;
import com.rsbuddy.script.methods.Players;
import com.rsbuddy.script.methods.Walking;
import com.rsbuddy.script.wrappers.Tile;

public class PathFinder {
	private final Tile start, destination;
	private final int[][] boundaries;
	private final Tile base;
	private final int baseX, baseY;
	private Tile[] path;

	public static boolean enable = true;
	public final static ArrayList<Tile> encountered = new ArrayList<Tile>();

	private final static int BLOCK_0 = 0x100;
	private final static int BLOCK_1 = 0x40000;
	private final static int BLOCK_2 = 0x200000;
	private final static int BLOCK_3 = 0x40000000;

	private final static int BLOCK_0_NORTH = 0x2;
	private final static int BLOCK_0_NORTH_EAST = 0x4;
	private final static int BLOCK_0_EAST = 0x8;
	private final static int BLOCK_0_SOUTH_EAST = 0x10;
	private final static int BLOCK_0_SOUTH = 0x20;
	private final static int BLOCK_0_SOUTH_WEST = 0x40;
	private final static int BLOCK_0_WEST = 0x80;
	private final static int BLOCK_0_NORTH_WEST = 0x200;

	private final static int BLOCK_1_NORTH = 0x400;
	private final static int BLOCK_1_NORTH_EAST = 0x800;
	private final static int BLOCK_1_EAST = 0x1000;
	private final static int BLOCK_1_SOUTH_EAST = 0x2000;
	private final static int BLOCK_1_SOUTH = 0x4000;
	private final static int BLOCK_1_SOUTH_WEST = 0x8000;
	private final static int BLOCK_1_WEST = 0x10000;
	private final static int BLOCK_1_NORTH_WEST = 0x40000;

	private final static int BLOCK_2_NORTH = 0x800000;
	private final static int BLOCK_2_NORTH_EAST = 0x1000000;
	private final static int BLOCK_2_EAST = 0x2000000;
	private final static int BLOCK_2_SOUTH_EAST = 0x4000000;
	private final static int BLOCK_2_SOUTH = 0x8000000;
	private final static int BLOCK_2_SOUTH_WEST = 0x10000000;
	private final static int BLOCK_2_WEST = 0x20000000;
	private final static int BLOCK_2_NORTH_WEST = 0x80000000;

	private final static int FULL_BLOCK = BLOCK_0 + BLOCK_1 + BLOCK_2 + BLOCK_3;

	private final static int BLOCK_NORTH = FULL_BLOCK + BLOCK_0_NORTH + BLOCK_1_NORTH
			+ BLOCK_2_NORTH;
	private final static int BLOCK_SOUTH = FULL_BLOCK + BLOCK_0_SOUTH + BLOCK_1_SOUTH
			+ BLOCK_2_SOUTH;
	private final static int BLOCK_WEST = FULL_BLOCK + BLOCK_0_WEST + BLOCK_1_WEST + BLOCK_2_WEST;
	private final static int BLOCK_EAST = FULL_BLOCK + BLOCK_0_EAST + BLOCK_1_EAST + BLOCK_2_EAST;

	private final static int BLOCK_NORTH_EAST = FULL_BLOCK + BLOCK_0_NORTH_EAST
			+ BLOCK_1_NORTH_EAST + BLOCK_2_NORTH_EAST;
	private final static int BLOCK_SOUTH_EAST = FULL_BLOCK + BLOCK_0_SOUTH_EAST
			+ BLOCK_1_SOUTH_EAST + BLOCK_2_SOUTH_EAST;
	private final static int BLOCK_SOUTH_WEST = FULL_BLOCK + BLOCK_0_SOUTH_WEST
			+ BLOCK_1_SOUTH_WEST + BLOCK_2_SOUTH_WEST;
	private final static int BLOCK_NORTH_WEST = FULL_BLOCK + BLOCK_0_NORTH_WEST
			+ BLOCK_1_NORTH_WEST + BLOCK_2_NORTH_WEST;

	/**
	 * Used to find a path between the position of the player and the given
	 * destination tile.
	 * 
	 * @param destination
	 *            the of destination
	 */
	public PathFinder(final Tile destination) {
		this(Players.getLocal().getLocation(), destination);
	}

	public boolean active = true;

	/**
	 * Used to find a path between start tile and destination tile.
	 * 
	 * @param start
	 *            Tile to start with.
	 * @param end
	 *            Tile of destination
	 */
	public PathFinder(Tile start, Tile destination) {
		if (start == null || destination == null) {
			throw new NullPointerException("Tile: " + (start == null ? "start" : "end") + "is null");
		}
		this.start = start;
		this.destination = destination;
		base = Game.getMapBase();
		baseX = base.getX() - 1;
		baseY = base.getY() - 1;
		boundaries = Walking.getCollisionFlags(Game.getFloorLevel());
		int i = start.getX() - baseX;
		int j = start.getY() - baseY;
		if (i < 1 || j < 1 || i > 104 || j > 104) {
			return;
		}
		active = true;
		new Thread() {
			@Override
			public void run() {
				initializePath();
				active = false;
			}
		}.start();

	}

	public Tile[] getPath() {
		return path;
	}

	private void initializePath() {
		NodeTile curr = new NodeTile(start, null);
		while (true) {
			if (curr == null || !enable) {
				return;
			}
			NodeTile node = curr.getNearest();
			if (node == null) {
				curr = curr.getPrevious();
				continue;
			}
			curr = node;
			if (curr.equals(destination)) {
				LinkedList<Tile> tiles = new LinkedList<Tile>();
				tiles.add(curr);
				for (NodeTile tile = node.getPrevious(); tile != null; tile = tile.getPrevious()) {
					tiles.add(new Tile(tile.getX(), tile.getY()));
				}

				LinkedList<Tile> reverse = new LinkedList<Tile>();
				for (int i = 0; i < tiles.size(); i++) {
					reverse.add(tiles.removeLast());
				}
				encountered.clear();
				encountered.addAll(reverse);
				path = reverse.toArray(new Tile [reverse.size()]);
				return;
			}
		}

	}

	private class NodeTile extends Tile {
		private final NodeTile previous;
		private final boolean used[] = new boolean [8];

		private NodeTile(Tile tile, NodeTile previous) {
			super(tile.getX(), tile.getY());
			this.previous = previous;
		}

		private NodeTile getPrevious() {
			return previous;
		}

		private NodeTile getNearest() {

			final Tile[] tiles = {new Tile(getX(), getY() + 1), new Tile(getX() + 1, getY() + 1),
					new Tile(getX() + 1, getY()), new Tile(getX() + 1, getY() - 1),
					new Tile(getX(), getY() - 1), new Tile(getX() - 1, getY() - 1),
					new Tile(getX() - 1, getY()), new Tile(getX() - 1, getY() + 1)};

			final int[] check = {BLOCK_NORTH, BLOCK_SOUTH, BLOCK_NORTH_EAST, BLOCK_SOUTH_WEST,
					BLOCK_EAST, BLOCK_WEST, BLOCK_SOUTH_EAST, BLOCK_NORTH_WEST, BLOCK_SOUTH,
					BLOCK_NORTH, BLOCK_SOUTH_WEST, BLOCK_NORTH_EAST, BLOCK_WEST, BLOCK_EAST,
					BLOCK_NORTH_WEST, BLOCK_SOUTH_EAST};

			Tile closest = null;
			double nearest = 999999;
			int index = -1;
			Top: for (int i = 0; i < 8; i++) {
				Tile tile = tiles[i];
				int x = tile.getX() - baseX;
				int y = tile.getY() - baseY;
				if (x < 1 || y < 1 || x > 102 || y > 102 || used[i]) {
					continue;
				}
				if ((boundaries[getX() - baseX][getY() - baseY] & check[i * 2]) != 0
						|| (boundaries[x][y] & check[i * 2 + 1]) != 0) {
					continue;
				}
				for (NodeTile node = this; node != null; node = node.getPrevious()) {
					if (node.equals(tile)) {
						continue Top;
					}
				}

				double distance = Math.sqrt((tile.getX() - destination.getX())
						* (tile.getX() - destination.getX()) + (tile.getY() - destination.getY())
						* (tile.getY() - destination.getY()));
				if (distance < nearest) {
					nearest = distance;
					closest = tile;
					index = i;
				}
			}
			if (index > -1) {
				used[index] = true;
			}
			return closest != null ? new NodeTile(closest, this) : null;
		}
	}
}
